using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Criterion;
using Project.Library.Core;
using Project.Library.Core.DBC;

namespace Project.Library.NHibernate
{
    /// <summary>
    /// Allows to optimize the access to the DB batching the interogation queries
    /// </summary>
    /// <remarks>
    /// <code>
    /// ....
    /// DetachedCriteria GetUsersDetachedCriteria = ....
    /// 
    /// var qBatcher = new QueryBatcher(session); 
    /// qBatcher.AddQuery("GetUsers", GetUsersDetachedCriteria)
    ///     .AddQuery("GetOrders", GetOrdersDetachedCriteria)
    ///     .AddQuery("GetSuppliers", GetSuppliersDetachedCriteria)
    /// 
    /// ... 
    /// var orders = qBatcher.GetListResult<Order>("GetOrders");
    /// ...
    /// </code>
    /// </remarks>
    public class QueryBatcher : Disposable, IQueryBatcher
    {
        private readonly IDictionary<string, ICriteria> queries;
        private readonly IDictionary<string, IList> queryResults;
        private readonly IList<string> queryNames;
        private readonly ISession session;
        private bool processed;

        /// <summary>
        /// Initializes a new instance of the <see cref="QueryBatcher"/> class.
        /// </summary>
        /// <param name="session">The active session manager.</param>
        public QueryBatcher(ISession session)
        {
            queries = new Dictionary<string, ICriteria>();
            queryResults = new Dictionary<string, IList>();
            queryNames = new List<string>();
            this.session = session;
            processed = false;
        }

        /// <summary>
        /// Register a DetachedCriteria for batching.
        /// </summary>
        /// <param name="queryName">Name of the query.</param>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public IQueryBatcher AddQuery(string queryName, DetachedCriteria criteria)
        {
            AddQuery(queryName, criteria.GetExecutableCriteria(session));
            return this;
        }

        /// <summary>
        /// Register a DetachedCriteria for batching.
        /// </summary>
        /// <param name="queryName">Name of the query.</param>
        /// <param name="criteria">The criteria.</param>
        /// <returns></returns>
        public IQueryBatcher AddQuery(string queryName, ICriteria criteria)
        {
            Checks.Require(processed,
                           "The registered queries have already been processed. You can't add new Criterias to be perform with this QueryBatcher");
            Checks.Require(!queryNames.Contains(queryName),
                           string.Format("A query called {0} is already registered", queryName));

            queryNames.Add(queryName);
            queries[queryName] = criteria;
            return this;
        }

        /// <summary>
        /// Returns the result enumeration corresponding to the queryName.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queryName">Name of the query.</param>
        /// <returns></returns>
        public IEnumerable<T> GetListResult<T>(string queryName)
        {
            ProcessQueries(queryName);

            if (queryResults.ContainsKey(queryName))
                return queryResults[queryName] as IEnumerable<T>;

            return null;
        }

        /// <summary>
        /// Returns the unique result corresponding to the queryName.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="queryName">Name of the query.</param>
        /// <returns></returns>
        public T GetUniqueResult<T>(string queryName)
        {
            ProcessQueries(queryName);

            if (queryResults.ContainsKey(queryName))
            {
                var result = queryResults[queryName];
                return (T)(result.Count > 0 ? result[0] : default(T));
            }
            return default(T);
        }

        /// <summary>
        /// Check  Processes the queries.
        /// </summary>
        /// <param name="queryName">Name of the query.</param>
        private void ProcessQueries(string queryName)
        {
            Checks.Require(!queryNames.Contains(queryName),
                           string.Format("There is no registered DetachedCriteria for the query '{0}'",
                                         queryName));
            ExecuteQueries();
        }

        /// <summary>
        /// Executes the queries if it's necessary.
        /// </summary>
        private void ExecuteQueries()
        {
            if(processed) return;

            var multiCriteria = session.CreateMultiCriteria();
            foreach (var query in queries)
            {
                multiCriteria.Add(query.Value);
            }
            var results = multiCriteria.List();
            int count = results.Count;

            for (int i = 0; i < count; i++)
            {
                var key = queryNames[i];
                queryResults[key] = results[i] as IList;
            }
            processed = true;
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected override void Dispose(bool disposing)
        {
            if(! disposing) return;
            queries.Clear();
            queryResults.Clear();
        }
    }
}